import java.awt.AWTException;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class LightThread implements Runnable {

	// Minimum LED brightness; some users prefer a small amount of backlighting
	// at all times, regardless of screen content.  Higher values are brighter,
	// or set to 0 to disable this feature.

	static final short minBrightness = 20;

	int leds[][];

	// PER-DISPLAY INFORMATION ---------------------------------------------------

	// This array contains details for each display that the software will
	// process.  If you have screen(s) attached that are not among those being
	// "Adalighted," they should not be in this list.  Each triplet in this
	// array represents one display.  The first number is the system screen
	// number...typically the "primary" display on most systems is identified
	// as screen #1, but since arrays are indexed from zero, use 0 to indicate
	// the first screen, 1 to indicate the second screen, and so forth.  This
	// is the ONLY place system screen numbers are used...ANY subsequent
	// references to displays are an index into this list, NOT necessarily the
	// same as the system screen number.  For example, if you have a three-
	// screen setup and are illuminating only the third display, use '2' for
	// the screen number here...and then, in subsequent section, '0' will be
	// used to refer to the first/only display in this list.
	// The second and third numbers of each triplet represent the width and
	// height of a grid of LED pixels attached to the perimeter of this display.
	// For example, '9,6' = 9 LEDs across, 6 LEDs down.

	static final int displays[][] = new int[][] {
		{0,9,20} // Screen 0, 9 LEDs across, 6 LEDs down
		//,{1,9,6} // Screen 1, also 9 LEDs across and 6 LEDs down
	};

	static final short fade = 100;
	byte[]           serialData;


	int[]            x = new int[16], y = new int[16],screenData;
	float            f, range, step, start;
	int              d, i, totalWidth, maxHeight, row, col, rowOffset;
	int[][]          pixelOffset;
	short[][]        ledColor, prevColor;
	byte[][]         gamma       = new byte[256][3];


	static int[] res = new int[2];
	Robot bot;
	Rectangle screenRect;
	ledOverview ledView;
	modeMon mon;
	final static int amb=0, str=1, colm=2, pul=3;
	ArrayList<Color> colorlist;
	


	public LightThread(int[][] led, ledOverview leddar, modeMon mon) {
		ledView=leddar;
		this.mon=mon;
		leds=led;
		serialData  = new byte[6 + leds.length * 3];
		pixelOffset = new int[leds.length][256];
		ledColor    = new short[leds.length][3];
		prevColor   = new short[leds.length][3];
		
		colorlist = new ArrayList<Color>();

		try {
			bot = new Robot();
		} catch (AWTException e) {
			System.out.println("new Robot() failed");
			e.printStackTrace();
		}

	}

	public void setup(){

		GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
		res[0] = gd.getDisplayMode().getWidth();
		res[1] = gd.getDisplayMode().getHeight();
		screenRect = new Rectangle(res[0], res[1]);


		// Precompute locations of every pixel to read when downsampling.
		// Saves a bunch of math on each frame, at the expense of a chunk
		// of RAM.  Number of samples is now fixed at 256; this allows for
		// some crazy optimizations in the downsampling code.
		for(int i=0; i<leds.length; i++) { // For each LED...
			d = leds[i][0]; // Corresponding display index

			// Precompute columns, rows of each sampled point for this LED
			range = res[0] / (float)displays[d][1];
			step  = (float) (range / 16.0);
			start = (float) (range * (float)leds[i][1] + step * 0.5);
			for(col=0; col<16; col++) x[col] = (int)(start + step * (float)col);
			range = res[1] / (float)displays[d][2];
			step  = (float) (range / 16.0);
			start = (float) (range * (float)leds[i][2] + step * 0.5);
			for(row=0; row<16; row++) y[row] = (int)(start + step * (float)row);

			// Get offset to each pixel within full screen capture
			for(row=0; row<16; row++) {
				for(col=0; col<16; col++) {
					pixelOffset[i][row * 16 + col] =
							y[row] * res[0] + x[col];
				}
			}
		}

		for(i=0; i<prevColor.length; i++) {
			prevColor[i][0] = prevColor[i][1] = prevColor[i][2] = minBrightness / 3;
		}

		// Pre-compute gamma correction table for LED brightness levels:
		for(i=0; i<256; i++) {
			f = (float) Math.pow(i / 255.0, 2.8);
			gamma[i][0] = (byte)(f * 255.0);
			gamma[i][1] = (byte)(f * 240.0);
			gamma[i][2] = (byte)(f * 220.0);
		}
	}

	void draw () {
		BufferedImage img;
		int           i, j, o, c, weight, rb, g, sum, deficit, s2;
		int[]         pxls, offs;
		img = bot.createScreenCapture(screenRect);

		// Get location of source pixel data
		screenData = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();

		weight = 257 - fade; // 'Weighting factor' for new frame vs. old
		j      = 6;          // Serial led data follows header / magic word

		// This computes a single pixel value filtered down from a rectangular
		// section of the screen.  While it would seem tempting to use the native
		// image scaling in Processing/Java, in practice this didn't look very
		// good -- either too pixelated or too blurry, no happy medium.  So
		// instead, a "manual" downsampling is done here.  In the interest of
		// speed, it doesn't actually sample every pixel within a block, just
		// a selection of 256 pixels spaced within the block...the results still
		// look reasonably smooth and are handled quickly enough for video.

		for(i=0; i<leds.length; i++) {  // For each LED...
			// Get location of source data from prior full-screen capture:
			pxls = screenData;
			offs = pixelOffset[i];
			rb = g = 0;
			for(o=0; o<256; o++) {
				c   = pxls[offs[o]];
				rb += c & 0x00ff00ff; // Bit trickery: R+B can accumulate in one var
				g  += c & 0x0000ff00;
			}
			// Blend new pixel value with the value from the prior frame
			ledColor[i][0]  = (short)((((rb >> 24) & 0xff) * weight +
					prevColor[i][0]     * fade) >> 8);
			ledColor[i][1]  = (short)(((( g >> 16) & 0xff) * weight +
					prevColor[i][1]     * fade) >> 8);
			ledColor[i][2]  = (short)((((rb >>  8) & 0xff) * weight +
					prevColor[i][2]     * fade) >> 8);

			// Boost pixels that fall below the minimum brightness
			sum = ledColor[i][0] + ledColor[i][1] + ledColor[i][2];
			if(sum < minBrightness) {
				if(sum == 0) { // To avoid divide-by-zero
					deficit = minBrightness / 3; // Spread equally to R,G,B
					ledColor[i][0] += deficit;
					ledColor[i][1] += deficit;
					ledColor[i][2] += deficit;
				} else {
					deficit = minBrightness - sum;
					s2      = sum * 2;
					// Spread the "brightness deficit" back into R,G,B in proportion to
					// their individual contribition to that deficit.  Rather than simply
					// boosting all pixels at the low end, this allows deep (but saturated)
					// colors to stay saturated...they don't "pink out."
					ledColor[i][0] += deficit * (sum - ledColor[i][0]) / s2;
					ledColor[i][1] += deficit * (sum - ledColor[i][1]) / s2;
					ledColor[i][2] += deficit * (sum - ledColor[i][2]) / s2;
				}
			}
			// Apply gamma curve and place in serial output buffer
			serialData[j++] = gamma[ledColor[i][0]][0];
			serialData[j++] = gamma[ledColor[i][1]][1];
			serialData[j++] = gamma[ledColor[i][2]][2];
			// Update pixels in preview image
			//preview[d].pixels[leds[i][2] * displays[d][1] + leds[i][1]] = (ledColor[i][0] << 16) | (ledColor[i][1] << 8) | ledColor[i][2];

			Color temp = new Color(ledColor[i][0],ledColor[i][1],ledColor[i][2]);
			colorlist.add(i,temp);
			
			if(i==leds.length-1 && mon.getMode()==amb){
				mon.setColor(colorlist);
				ledView.Repaint();
			}
		}


	}



	@Override
	public void run() {
		while(true){
			int duration;
			if(mon.getMode()==amb){
				draw();
				duration = 10;
			}else{
				duration =900;
			}
			try {
				Thread.sleep(duration);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}

	}

}
